//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this sample source code is subject to the terms of the Microsoft
// license agreement under which you licensed this sample source code. If
// you did not accept the terms of the license agreement, you are not
// authorized to use this sample source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the LICENSE.RTF on your install media or the root of your tools installation.
// THE SAMPLE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES.
//
//////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2003 Intrinsyc Europe Ltd.
//////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2004 Siemens AG
//
// Version: 1.1.2.5
// Date:    11. Jan. 2006
//////////////////////////////////////////////////////////////////////////////


#include "precomp.h"

//
// Command types
//
static const DWORD g_rgdwCommandTypes[] =
{
    0x00,   // RIL_MSGCMDTYPE_STATUSREQ
    0x01,   // RIL_MSGCMDTYPE_CANCELSTATUSREQ
    0x02,   // RIL_MSGCMDTYPE_DELETEMESSAGE
    0x03,   // RIL_MSGCMDTYPE_ENABLESTATUSREQ
};
#define NUM_COMMANDTYPES    (sizeof(g_rgdwCommandTypes) / sizeof(DWORD))

//
// Geographical Scope
//
static const DWORD g_rgdwGeographicalScopes[] =
{
    RIL_GEOSCOPE_CELL_IMMEDIATE,
    RIL_GEOSCOPE_PLMN,
    RIL_GEOSCOPE_LOCATIONAREA,
    RIL_GEOSCOPE_CELL,
};
#define NUM_GEOGRAPHICALSCOPES  (sizeof(g_rgdwGeographicalScopes) / sizeof(DWORD))

//
// DCS Languages
//
static const DWORD g_rgdwDCSLanguages[] =
{
    RIL_DCSLANG_GERMAN,
    RIL_DCSLANG_ENGLISH,
    RIL_DCSLANG_ITALIAN,
    RIL_DCSLANG_FRENCH,
    RIL_DCSLANG_SPANISH,
    RIL_DCSLANG_DUTCH,
    RIL_DCSLANG_SWEDISH,
    RIL_DCSLANG_DANISH,
    RIL_DCSLANG_PORTUGUESE,
    RIL_DCSLANG_FINNISH,
    RIL_DCSLANG_NORWEGIAN,
    RIL_DCSLANG_GREEK,
    RIL_DCSLANG_TURKISH,
    RIL_DCSLANG_HUNGARIAN,
    RIL_DCSLANG_POLISH,
    RIL_DCSLANG_UNKNOWN,
};
#define NUM_LANGUAGES (sizeof(g_rgdwDCSLanguages) / sizeof(DWORD))

//
// Constants and macros for parsing Cell Broadcast messages
//
const UINT CELLBROADCAST_HEADER_LENGTH = 6;

#define MESSAGECODE_FROM_SERIALNUMBER(sn)   ((sn >> 4) & 0x3ff)
#define GEOSCOPE_FROM_SERIALNUMBER(sn)      g_rgdwGeographicalScopes[((sn >> 14) & 0x3)]
#define UPDATENUMBER_FROM_SERIALNUMBER(sn)  (sn & 0xf)

#define PAGENUMBER(b)   ((b >> 4) & 0xf)
#define TOTALPAGES(b)   (b & 0xf)


//
// Table used to map semi-byte values to BCD characters
//
static const WCHAR g_rgwchSemiByteToBCDMap[16] = { L'0', L'1', L'2', L'3', L'4', L'5', L'6', L'7', L'8', L'9', L'*', L'#', L'a', L'b', L'c', L' ' };


//
// Convert a semi-byte into a BCD value
//
static WCHAR SemiByteToBCD(const BYTE bByte, const BOOL fHigh)
{
    TBD_FUNCTION(SemiByteToBCD);
    // If a semi-octect is a non-integer value != 0x0f, then the follow mapppings hold.
    // If a semi-octect prior to the last octect contains 0x0f, we skip that octet and continue on.
    // GSM 03.40 section 9.1.2.3

    BYTE bSemiByte = (fHigh ? (bByte & 0xf0) >> 4 : bByte & 0x0f);
    TBD_ASSERT(0x10 > bSemiByte);
    return g_rgwchSemiByteToBCDMap[bSemiByte];
}


//
// Fill in RILADDRESS structure from BYTE array
// see GSM 03.40 section 9.1.2.5 and GSM 04.11 8.2.5.2
//
static BOOL ParseMsgAddress(const BYTE* const pbIn, const BOOL fBeforePDU, RILADDRESS& rraAddress, UINT& rcbParsed, BOOL fGSM3dot40)
{
    TBD_FUNCTION(ParseMsgAddress);
    TBD_ASSERT(NULL != pbIn);

    UINT i;
    BYTE bType;
    BYTE bNumPlan = pbIn[1] & 0x0f;
    const BYTE* pbWalk;
    LPWSTR pwchWalk;
    UINT cchUsed;
    UINT cchLen;
    UINT cbAddress;
    WCHAR wchBCD;
    BOOL fRet = FALSE;

    // Initalize returned values
    rcbParsed = 0;
    (void)memset(&rraAddress, 0x00, sizeof(RILADDRESS));
    rraAddress.cbSize = sizeof(RILADDRESS);

    // If address length is 0, just get out. This shouldn't happen, but we've seen it.
    if (*pbIn==0)
    {
        TBD_OUTPUT(TBDCT_INFO, TBDOL_ERROR, TEXT("ParseMsgAddress : Address length is 0?"));
        if (fGSM3dot40)
        {
            // GSM 03.40 section 9.2.2.1 indicates that there will always be a MINIMUM of 2 octets for the TP-OA field
            // Some SMS messages we've seen in the wild have a TP-OA Address-Length of 0, Type-of-Address of International
            // and 0 bytes of data.  Setting rcbParsed to 2 below (rather than 1) is necessary to correctly parse these
            // messages.
            rcbParsed = 2;
        }
        else
        {
            // GSM 04.11 8.2.5.2 is ambiguous about whether a zero length address will include an address type and be
            // two bytes, or will just have a length and be one byte.  However we have seen at least one case in which
            // an address did not have a type, just a length.  There are no known instances yet when it has both a type
            // and a length, so for now we are assuming it will always be just one byte.
            rcbParsed = 1;
        }
        return TRUE;
    }

    // Determine the address type
    bType = (pbIn[1] & 0x70) >> 4;

    for (i = 0; i < NUM_ADDRTYPES; i++) {
        if (g_rgbAddrTypes[i] == bType) {
            rraAddress.dwType = i;
            rraAddress.dwParams |= RIL_PARAM_A_TYPE;
            break;
        }
    }
    TBD_ASSERT(0 != (rraAddress.dwParams & RIL_PARAM_A_TYPE));

    if ((RIL_ADDRTYPE_UNKNOWN == rraAddress.dwType) ||
        (RIL_ADDRTYPE_INTERNATIONAL == rraAddress.dwType) ||
        (RIL_ADDRTYPE_NATIONAL == rraAddress.dwType)) {
        // Determine the numbering plan
        for (i = 0; i < NUM_NUMPLANS; i++) {
            if (g_rgbNumPlans[i] == bNumPlan) {
                rraAddress.dwNumPlan = i;
                rraAddress.dwParams |= RIL_PARAM_A_NUMPLAN;
                break;
            }
        }

        // If the number plan is not one that is listed in 03.40,
        // then it is a reserved value, which we are supposed to
        // treat as 'Unknown'.
        if (RIL_PARAM_A_NUMPLAN != (rraAddress.dwParams & RIL_PARAM_A_NUMPLAN))
        {
            rraAddress.dwNumPlan = RIL_NUMPLAN_UNKNOWN;
            rraAddress.dwParams |= RIL_PARAM_A_NUMPLAN;
            TBD_OUTPUT(TBDCT_INFO, TBDOL_INFORMATION, TEXT("ParseMsgAddress : Parsed reserved numbering plan: 0x0%x"), bNumPlan);
        }
    }

    if (rraAddress.dwType == RIL_ADDRTYPE_ALPHANUM) {
        // It seems that alphanumeric address length is the number of real bytes used after the Type-of-Address byte
        cbAddress = *pbIn / 2;
        cchLen = cbAddress * 8 / 7;

        if (!(ConvertToUnicode(ENCODING_GSMDEFAULT, (LPCSTR) (pbIn + 2), cbAddress, rraAddress.wszAddress,
                               min(cchLen, MAXLENGTH_ADDRESS), cchUsed))) {
            goto Error;
        }
    } else {
        if (!fBeforePDU) {
            // Calculate size of semi-octet-encoded address, per GSM 03.40 section 9.1.2.3
            cbAddress = *pbIn / 2 + *pbIn % 2;
        } else {
            cbAddress = *pbIn - 1;
        }

        pbWalk = pbIn + 2;
        pwchWalk = rraAddress.wszAddress;

        for (i = 0; i < cbAddress; i++) {
            wchBCD = SemiByteToBCD(*pbWalk, FALSE);
            if (L' ' != wchBCD) {
                *pwchWalk++ = wchBCD;
            }
            wchBCD = SemiByteToBCD(*pbWalk, TRUE);
            if (L' ' != wchBCD) {
                *pwchWalk++ = wchBCD;
            }
            pbWalk++;
        }

        // NULL-terminate the address string
        *pwchWalk = L'\0';
    }
    rraAddress.dwParams |= RIL_PARAM_A_ADDRESS;

    rcbParsed = cbAddress + 2;
    fRet = TRUE;

Error:
    return fRet;
}


//
// Fill in SYSTEMTIME structure from BYTE array
// see GSM 03.40 section 9.2.3.11
//
static BOOL ParseMsgTimeStamp(const BYTE* const pbIn, SYSTEMTIME& rstTime, UINT& rcbParsed)
{
    TBD_FUNCTION(ParseMsgTimeStamp);
    TBD_ASSERT(NULL != pbIn);

    UINT nYear;
    const BYTE* pbWalk = pbIn;
    UINT nOffset;
    BOOL fOffsetNegative;
    FILETIME_ULARGEINT ftuliTime; memset(&ftuliTime,0,sizeof(ftuliTime)); // zero struct
    ULARGE_INTEGER uliOffset; memset(&uliOffset,0,sizeof(uliOffset)); // zero struct
    BOOL fRet = FALSE;

    rcbParsed = 0;
    (void)memset(&rstTime, 0x00, sizeof(SYSTEMTIME));

    // Parse year
    // We don't expect to see any messages with a timestamp prior to 1991.
    // This should give plenty of buffer for abnormally early timestamps while still leaving
    // working functionality until 2090.
    nYear = (*pbWalk & 0x0f) * 10 + ((*pbWalk & 0xf0) >> 4);
    if (nYear > 90) {
        rstTime.wYear = 1900 + nYear;
    } else {
        rstTime.wYear = 2000 + nYear;
    }
    pbWalk++;

    // Parse month
    rstTime.wMonth = (*pbWalk & 0x0f) * 10 + ((*pbWalk & 0xf0) >> 4);
    pbWalk++;

    // Parse day
    rstTime.wDay = (*pbWalk & 0x0f) * 10 + ((*pbWalk & 0xf0) >> 4);
    pbWalk++;

    // Parse hours
    rstTime.wHour = (*pbWalk & 0x0f) * 10 + ((*pbWalk & 0xf0) >> 4);
    pbWalk++;

    // Parse minutes
    rstTime.wMinute = (*pbWalk & 0x0f) * 10 + ((*pbWalk & 0xf0) >> 4);
    pbWalk++;

    // Parse seconds
    rstTime.wSecond = (*pbWalk & 0x0f) * 10 + ((*pbWalk & 0xf0) >> 4);
    pbWalk++;

#if defined(TRUST_TIMEZONE_FIELD)
    /*
    The following code to adjust rstTime according to the time-zone information
    from the input is unused because most shipping GSM phones seem to ignore
    that field.  Specifically, all SMSCs seem to fill in the TP-SCTS field based
    on their *local* time and then set the offset from GMT to *0*.  Unless the
    SMSC is actually based in the GMT+0 time zone, this behavior is incorrect.
    Because our devices know which time zone they're in, if they use the time
    zone information provided, then they will calculate a time that is adjusted
    for the device's offset from the SMSC's (incorrectly reported) offset and
    will obtain a resulting time that is incorrect.  This leads to SMS messages
    appearing in Inbox with incorrect time stamps and ends up confusing users.
    */

    // Parse timezone offset (LocalTime - GMT in in quarters of an hour)
    // See GSM 03.40 ver5.8.1 ch9.2.3.11
    nOffset = ((*pbWalk & 0x07) * 10 + ((*pbWalk & 0xf0) >> 4)) * 15;
    fOffsetNegative = (*pbWalk & 0x08);
#else // defined(TRUST_TIMEZONE_FIELD)
    /*
    Until SMSCs start getting properly configured, the best option seems to be
    to effectively ignore the time zone information (as other GSM phones do).
    However, in order for us to ignore the time zone field, we actually need
    to fill in the SYSTEMTIME structure with the UTC representation of the local
    time we just parsed.  That means offsetting it by the current time zone bias
    of the device.
    */

    {
    TIME_ZONE_INFORMATION tzi;
    LONG lBias = 0;
    const DWORD dwGetTimeZoneInformationResult = GetTimeZoneInformation(&tzi);
    if(TIME_ZONE_ID_STANDARD == dwGetTimeZoneInformationResult) {
        lBias = tzi.Bias+tzi.StandardBias;
    } else if(TIME_ZONE_ID_DAYLIGHT == dwGetTimeZoneInformationResult) {
        lBias = tzi.Bias+tzi.DaylightBias;
    } else if(TIME_ZONE_ID_UNKNOWN == dwGetTimeZoneInformationResult) {
        lBias = tzi.Bias;
    } else {  // Would like to check for error with TIME_ZONE_ID_INVALID, but it's not defined by our OS headers
        TBD_ASSERT(!"Should never reach here");
    }
    fOffsetNegative = (lBias < 0);
    nOffset = ((fOffsetNegative) ? (-lBias) : (lBias));
    // Now that we've used fOffsetNegative to set nOffset=abs(lBias), we need
    // to flip fOffsetNegative because we want to calculate the UTC value that
    // represents the same temporal time as the local clock time we just
    // extracted.
    fOffsetNegative = !fOffsetNegative;
    }
#endif // defined(TRUST_TIMEZONE_FIELD)
    pbWalk++;


    // Convert our local time to filetime
    if (!SystemTimeToFileTime(&rstTime, &ftuliTime.ft)) {
        goto Error;
    }

    // Convert timezone offset to filetime
    uliOffset.QuadPart = nOffset * (__int64)600000000;

    // Apply the timezone offset
    // (NOTE: the offset is LocalTime - GMT, so we need to *add* it to the LocalTime if the offset is negative,
    //        and *subtract* it if the offset is positive)
    if (fOffsetNegative) {
        ftuliTime.uli.QuadPart += uliOffset.QuadPart;
    } else {
        ftuliTime.uli.QuadPart -= uliOffset.QuadPart;
    }

    // Convert the updated filetime back to the SYSTEMTIME structure
    if (!FileTimeToSystemTime(&ftuliTime.ft, &rstTime)) {
        goto Error;
    }

    rcbParsed = pbWalk - pbIn;
    fRet = TRUE;

Error:
    return fRet;
}



//
// Set the ProtocolID of an Incoming SMS Message
// see GSM 03.40 section 9.2.3.9
//
static BOOL ParseMsgProtocolID(const BYTE* const pbIn, DWORD& rdwProtocolID, UINT& rcbParsed)
{
    TBD_FUNCTION(ParseMsgProtocolID);
    TBD_ASSERT(NULL != pbIn);

    UINT i;
    BOOL fRet = FALSE;

    rcbParsed = 0;

    if ((0 == (*pbIn & 0xe0)) || (0x80 == (*pbIn & 0xc0))) {  // Reserved or unsupported values should be treated as 0x00
        rdwProtocolID = RIL_MSGPROTOCOL_SMETOSME;
        fRet = TRUE;
    } else {
        for (i = 0; i < NUM_PROTOCOLIDS; i++) {
            if (g_rgdwProtocolIDs[i] == *pbIn) {
                rdwProtocolID = i;
                fRet = TRUE;
                break;
            }
        }

        if (!fRet)
        {
            rdwProtocolID = RIL_MSGPROTOCOL_UNKNOWN;
            fRet = TRUE;
        }
    }

    if (fRet) {
        rcbParsed = 1;
    }
    return fRet;
}


//
// Set Data Coding Scheme of Incoming SMS Message
// see GSM 03.38
//
static BOOL ParseMsgDCS(const BYTE* const pbIn, RILMSGDCS& rrmdDCS, UINT& rcbParsed)
{
    TBD_FUNCTION(ParseMsgDCS);
    TBD_ASSERT(NULL != pbIn);

    BYTE bDCS = *pbIn;
    BOOL fRet = FALSE;

    rcbParsed = 0;
    (void)memset(&rrmdDCS, 0x00, sizeof(RILMSGDCS));
    rrmdDCS.cbSize = sizeof(RILMSGDCS);

    switch (bDCS & 0xf0)
    {
        case 0x00:
        case 0x10:
        case 0x20:
        case 0x30:
            rrmdDCS.dwType = RIL_DCSTYPE_GENERAL;
            rrmdDCS.dwParams |= RIL_PARAM_MDCS_TYPE;

            if (bDCS & 0x20) {
                rrmdDCS.dwFlags |= RIL_DCSFLAG_COMPRESSED;
                rrmdDCS.dwParams |= RIL_PARAM_MDCS_FLAGS;
            }

            if (bDCS & 0x10) {
                switch (bDCS & 0x03)
                {
                    case 0x00:
                        rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_0;
                        break;

                    case 0x01:
                        rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_1;
                        break;

                    case 0x02:
                        rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_2;
                        break;

                    case 0x03:
                        rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_3;
                        break;

                    default:
                        goto Error;
                }
                rrmdDCS.dwParams |= RIL_PARAM_MDCS_MSGCLASS;
            }

            switch (bDCS & 0x0c)
            {
                case 0x00:
                    rrmdDCS.dwAlphabet = RIL_DCSALPHABET_DEFAULT;
                    break;

                case 0x04:
                    rrmdDCS.dwAlphabet = RIL_DCSALPHABET_8BIT;
                    break;

                case 0x08:
//                  NKDbgPrintfW(TEXT("RilDrv: UCS2 encoded message\r\n"));
                    rrmdDCS.dwAlphabet = RIL_DCSALPHABET_UCS2;
                    break;

                default:
                    goto Error;
            }
            rrmdDCS.dwParams |= RIL_PARAM_MDCS_ALPHABET;
            break;

        case 0xc0:
            rrmdDCS.dwFlags |= RIL_DCSFLAG_DISCARD;
            rrmdDCS.dwParams |= RIL_PARAM_MDCS_FLAGS;
            //
            // Fall through
            //
        case 0xd0:
            rrmdDCS.dwAlphabet = RIL_DCSALPHABET_DEFAULT;
            rrmdDCS.dwParams |= RIL_PARAM_MDCS_ALPHABET;
            //
            // Fall through
            //
        case 0xe0:
            rrmdDCS.dwType = RIL_DCSTYPE_MSGWAIT;
            rrmdDCS.dwParams |= RIL_PARAM_MDCS_TYPE;

            if (!(rrmdDCS.dwParams & RIL_PARAM_MDCS_ALPHABET)) {
                rrmdDCS.dwAlphabet = RIL_DCSALPHABET_UCS2;
                rrmdDCS.dwParams |= RIL_PARAM_MDCS_ALPHABET;
            }

            if (bDCS & 0x08) {
                rrmdDCS.dwFlags |= RIL_DCSFLAG_INDICATIONACTIVE;
                rrmdDCS.dwParams |= RIL_PARAM_MDCS_FLAGS;
            }

            switch (bDCS & 0x03)
            {
                case 0x00:
                    rrmdDCS.dwIndication = RIL_DCSINDICATION_VOICEMAIL;
                    break;
                case 0x01:
                    rrmdDCS.dwIndication = RIL_DCSINDICATION_FAX;
                    break;
                case 0x02:
                    rrmdDCS.dwIndication = RIL_DCSINDICATION_EMAIL;
                    break;
                case 0x03:
                    rrmdDCS.dwIndication = RIL_DCSINDICATION_OTHER;
                    break;
                default:
                    goto Error;
            }
            rrmdDCS.dwParams |= RIL_PARAM_MDCS_INDICATION;
            break;

        case 0xf0:
            rrmdDCS.dwType = RIL_DCSTYPE_MSGCLASS;
            rrmdDCS.dwParams |= RIL_PARAM_MDCS_TYPE;

            if (bDCS & 0x04) {
                rrmdDCS.dwAlphabet = RIL_DCSALPHABET_8BIT;
            } else {
                rrmdDCS.dwAlphabet = RIL_DCSALPHABET_DEFAULT;
            }
            rrmdDCS.dwParams |= RIL_PARAM_MDCS_ALPHABET;

            switch (bDCS & 0x03)
            {
                case 0x00:
                    rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_0;
                    break;
                case 0x01:
                    rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_1;
                    break;
                case 0x02:
                    rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_2;
                    break;
                case 0x03:
                    rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_3;
                    break;
                default:
                    goto Error;
            }
            rrmdDCS.dwParams |= RIL_PARAM_MDCS_MSGCLASS;
            break;

        default:
            goto Error;
    }
    rcbParsed = 1;
    fRet = TRUE;

Error:
    return fRet;
}


//
// Set the Status value of a message
// see GSM 03.40 section 9.2.3.15
//
static BOOL ParseMsgStatus(const BYTE* const pbIn, DWORD& rdwStatus, UINT& rcbParsed)
{
    TBD_FUNCTION(ParseMsgStatus);
    TBD_ASSERT(NULL != pbIn);

    DWORD MsgStatus = (DWORD)*pbIn;
    DWORD Status;

    // Search the table for common status mappings
    for (Status = 1; Status < NUM_DLVSTATUS; Status++)
    {
        if (g_rgdwDlvStatus[Status] == MsgStatus)
        {
            break;
        }
    }

    // Did we find it in the table?
    if (Status<NUM_DLVSTATUS)
    {
        // Nothing to do
    }
    else if ((MsgStatus>=0x3) && (MsgStatus<=0xF))
    {
        Status = RIL_MSGDLVSTATUS_RESERVED_COMPLETED;
    }
    else if ((MsgStatus>=0x10) && (MsgStatus<=0x1F))
    {
        Status = RIL_MSGDLVSTATUS_SCSPECIFIC_COMPLETED;
    }
    else if ((MsgStatus>=0x26) && (MsgStatus<=0x2F))
    {
        Status = RIL_MSGDLVSTATUS_RESERVED_TRYING;
    }
    else if ((MsgStatus>=0x30) && (MsgStatus<=0x3F))
    {
        Status = RIL_MSGDLVSTATUS_SCSPECIFIC_TRYING;
    }
    else if ((MsgStatus>=0x4A) && (MsgStatus<=0x4F))
    {
        Status = RIL_MSGDLVSTATUS_RESERVED_ERROR;
    }
    else if ((MsgStatus>=0x50) && (MsgStatus<=0x5F))
    {
        Status = RIL_MSGDLVSTATUS_SCSPECIFIC_ERROR;
    }
    else if ((MsgStatus>=0x66) && (MsgStatus<=0x6F))
    {
        Status = RIL_MSGDLVSTATUS_RESERVED_TMPERROR;
    }
    else if ((MsgStatus>=0x70) && (MsgStatus<=0x7F))
    {
        Status = RIL_MSGDLVSTATUS_SCSPECIFIC_TMPERROR;
    }
    else
    {
        TBD_OUTPUT(TBDCT_INFO, TBDOL_ERROR, TEXT("ParseMsgStatus : Unrecognized message status 0x%x!?"),MsgStatus);
        rcbParsed=0;
        return FALSE;
    }

    rdwStatus = Status;
    rcbParsed = 1;
    return TRUE;
}


//
// Set the CommandType of an Incoming SMS Message
// see GSM 03.40 section 9.2.3.21
//
static BOOL ParseMsgCommandType(const BYTE* const pbIn, DWORD& rdwCommandType, UINT& rcbParsed)
{
    TBD_FUNCTION(ParseMsgCommandType);
    TBD_ASSERT(NULL != pbIn);

    UINT i;
    BOOL fRet = FALSE;

    rcbParsed = 0;

    for (i = 0; i < NUM_COMMANDTYPES; i++) {
        if (g_rgdwCommandTypes[i] == *pbIn) {
            rdwCommandType = i;
            fRet = TRUE;
            break;
        }
    }

    if (fRet) {
        rcbParsed = 1;
    }
    return fRet;
}


//
// Set Hdr data for an incoming message
//
static void ParseMsgHdr(const BYTE* const pbIn, const DWORD dwAlphabet, BYTE* const pbOut, DWORD& rcbHdrLength, UINT& rcbParsed)
{
    TBD_FUNCTION(ParseMsgHdr);
    TBD_ASSERT(NULL != pbIn);
    TBD_ASSERT(NULL != pbOut);

    const BYTE* pbWalk = pbIn;

    rcbHdrLength = *pbWalk;
    pbWalk++;
    rcbParsed = 1;

    if (rcbHdrLength) {
        memcpy(pbOut, pbWalk, min(rcbHdrLength, MAXLENGTH_HDR));
        rcbParsed += rcbHdrLength;
    }
}

//
//
//
void ShiftPackedBufferNBitsRight(BYTE *pbyBuffer, DWORD cBufferBytes, DWORD cShiftBits)
{
    BYTE *pbyCurrentByte = pbyBuffer;
    if (0 != cBufferBytes)
    {
        while (pbyCurrentByte < pbyBuffer + cBufferBytes - 1)
        {
            *pbyCurrentByte++ = ((*pbyCurrentByte >> cShiftBits) |
                                 (*(pbyCurrentByte + 1) << (8 - cShiftBits)));
        }
        *pbyCurrentByte = (*pbyCurrentByte >> cShiftBits);
    }
}

//
//
//
static BOOL ParseMsgHeaderAndBody(const BYTE* const pbIn, const BYTE* const pbEnd, const DWORD dwFlags, const RILMSGDCS& rmdDataCoding, DWORD& rdwParams,
                                  BYTE* const pbHdr, BYTE* const pbMsg, DWORD& rcbHdrLength, DWORD& rcchMsgLength,
                                  UINT& rcbParsed)
{
    TBD_FUNCTION(ParseMsgHeaderAndBody);
    TBD_ASSERT(NULL != pbIn);

    const BYTE* pbWalk = pbIn;
    UINT cchUserDataLength;
    DWORD dwAlphabet;
    UINT cbParsed;
    BOOL fRet = FALSE;
    DWORD dwBitsToShift = 0;

    rcbParsed = 0;

    if (rmdDataCoding.dwParams & RIL_PARAM_MDCS_ALPHABET) {
        dwAlphabet = rmdDataCoding.dwAlphabet;
    } else {
        // Default to 8-bit alphabet
        dwAlphabet = RIL_DCSALPHABET_8BIT;
    }

    // GSM 03.38 section 4 says that we don't need to parse the message body if fDiscard is set, but there's no sense losing data we've already got, so continue on regardless
    // fDiscard = (rmdDataCoding.dwParams & RIL_PARAM_MDCS_FLAGS) && (rmdDataCoding.dwFlags & RIL_DCSFLAG_DISCARD);

    // Figure out message length.  See GSM 03.40 section 9.2.3.24
    cchUserDataLength = *pbWalk++;

    // If there is less data in the buffer than is specified by the data length,
    // then abort now to avoid reading past the end of the buffer.
    if (RIL_DCSALPHABET_DEFAULT == dwAlphabet)
    {
        if (pbWalk + ((cchUserDataLength * 7 + 7) / 8) > pbEnd)
        {
            TBD_OUTPUT(TBDCT_INFO, TBDOL_ERROR, TEXT("ParseMsgHeaderAndBody : Data length is greater than remaining buffer length!"));
            goto Error;
        }
    }
    else
    {
        if (pbWalk + cchUserDataLength > pbEnd)
        {
            TBD_OUTPUT(TBDCT_INFO, TBDOL_ERROR, TEXT("ParseMsgHeaderAndBody : Data length is greater than remaining buffer length!"));
            goto Error;
        }
    }

    if (cchUserDataLength /* && !fDiscard */) {
        if (dwFlags & RIL_MSGFLAG_HEADER) {
            // Header present -- parse it
            ParseMsgHdr(pbWalk, dwAlphabet, pbHdr, rcbHdrLength, cbParsed);

            rdwParams |= (RIL_PARAM_M_HDR | RIL_PARAM_M_HDRLENGTH);
            pbWalk += cbParsed;

        } else {
            // No header present
            cbParsed = 0;
        }
        if (RIL_DCSALPHABET_DEFAULT == dwAlphabet) {
            // for phase 0 compatibility the data will start on a septet boundry
            // We want to pass it back starting at an octet boundry so we may
            // have to shift a little.
            dwBitsToShift = 7-((cbParsed * 8) % 7);
            if (dwBitsToShift == 7) dwBitsToShift = 0;
            ASSERT(((cbParsed * 8 + dwBitsToShift) % 7) == 0);
            rcchMsgLength =  cchUserDataLength - ((cbParsed * 8 + dwBitsToShift) / 7);
        } else if (RIL_DCSALPHABET_UCS2 == dwAlphabet) {
            TBD_ASSERT(0 == (cchUserDataLength - cbParsed) % 2);
            rcchMsgLength = (cchUserDataLength - cbParsed) / 2;
        } else {
            rcchMsgLength = cchUserDataLength - cbParsed;
        }

        // Parse message data
        if (rcchMsgLength) {
            if (RIL_DCSALPHABET_DEFAULT == dwAlphabet) {
                cbParsed = (rcchMsgLength * 7 + 7 + dwBitsToShift) / 8;
            } else if (RIL_DCSALPHABET_UCS2 == dwAlphabet) {
                cbParsed = rcchMsgLength * 2;
            } else {
                cbParsed = rcchMsgLength;
            }

            UINT cbToCopy = min(cbParsed,MAXLENGTH_MSG);

            TBD_ASSERT(pbEnd > pbWalk);
            TBD_ASSERT(cbToCopy <= (UINT)(pbEnd - pbWalk));

            (void)memcpy(pbMsg, pbWalk, cbToCopy);
            // make sure any GSM characters start on the byte boundry, not on the septet boundry.
            if (dwBitsToShift) {
                ShiftPackedBufferNBitsRight(pbMsg, cbToCopy, dwBitsToShift);
            }
            pbWalk += cbToCopy;

            rdwParams |= (RIL_PARAM_M_MSG | RIL_PARAM_M_MSGLENGTH);
        }
    }
    fRet = TRUE;
Error:
    rcbParsed = pbWalk - pbIn;
    return fRet;
}

//
//
//
// GSM 3.40, Section 9.2.3.12.1
//
// TP-VP Value  Validity period value                       Range
// ----------------------------------
//  0   - 143   (TP-VP + 1) x 5 minutes                     5 mins - 720 mins (.5 day)
//  144 - 167   12 hours + ((TP-VP - 143) x 30 minutes)     750 mins - 1440 mins (1 day)
//  168 - 196   (TP-VP - 166) x 1 day                       2 days -  30 days (43200 mins)
//  197 - 255   (TP-VP - 192) x 1 week                      35 days - 441 days (635040 mins)
//
static BOOL ParseRelativeValidityPeriod(const BYTE* const pbIn, SYSTEMTIME& rstVP, UINT& rcbParsed)
{
    UINT uVP = (UINT)*pbIn;
    UINT uMinutes = 0;

    if (uVP <= 143)
    {
        uMinutes = (uVP + 1) * 5;
    }
    else if (uVP <= 167)
    {
        uMinutes = 12 * 60 + (uVP - 143) * 30;
    }
    else if (uVP <= 196)
    {
        uMinutes = (uVP - 166) * 24 * 60;
    }
    else
    {
        uMinutes = (uVP - 192) * 7 * 24 * 60;
    }

    rstVP.wDay = uMinutes / 1440;
    uMinutes = uMinutes % 1440;
    rstVP.wHour = uMinutes / 60;
    rstVP.wMinute = uMinutes % 60;

    rcbParsed = 1;
    return TRUE;
}

static BOOL ParseEnhancedValidityPeriod(const BYTE* const pbIn, SYSTEMTIME& rstVP, UINT& rcbParsed)
{
    const BYTE * pbWalk = pbIn;
    const BYTE * pbEnd = pbWalk + 7;

    // Read function from the first octet
    const BYTE bFunction = *pbWalk;

    // Advance past extended functionality indicators
    for ( ; (*pbWalk & 0x80) && (pbWalk < pbEnd); pbWalk++)
        ;

    switch (bFunction & 0x7)
    {
        case 0x0:
            // None
            break;

        case 0x1:
            // Same as relative
            if (pbWalk < pbEnd)
            {
                (void)ParseRelativeValidityPeriod(pbIn, rstVP, rcbParsed);
            }
            break;

        case 0x2:
            // In seconds
            if (pbWalk < pbEnd)
            {
                rstVP.wSecond = *pbWalk;
            }
            break;

        case 0x3:
            // Semi-octet representation of hours, minutes, and seconds
            if (pbWalk + 3 <= pbEnd)
            {
                // Parse hours
                rstVP.wHour = (*pbWalk & 0x0f) * 10 + ((*pbWalk & 0xf0) >> 4);
                pbWalk++;

                // Parse minutes
                rstVP.wMinute = (*pbWalk & 0x0f) * 10 + ((*pbWalk & 0xf0) >> 4);
                pbWalk++;

                // Parse seconds
                rstVP.wSecond = (*pbWalk & 0x0f) * 10 + ((*pbWalk & 0xf0) >> 4);
                pbWalk++;

            }
            break;

        default:
            break;
    }

    rcbParsed = 7;
    return TRUE;
}

//
// Decode the Validity Period for a message
// see GSM 03.40 section 9.2.3.12
//
static BOOL ParseMsgValidityPeriod(const BYTE* const pbIn, const DWORD dwVPFormat, SYSTEMTIME& rstVP, UINT& rcbParsed)
{
    TBD_FUNCTION(ParseMsgValidityPeriod);
    TBD_ASSERT(NULL != pbIn);

    BOOL fRet = FALSE;

    rcbParsed = 0;

    switch (dwVPFormat)
    {
        case RIL_MSGVP_NONE:
            // Do nothing
            rcbParsed = 0;
            break;

        case RIL_MSGVP_RELATIVE:
            fRet = ParseRelativeValidityPeriod(pbIn, rstVP, rcbParsed);
            goto Error;

        case RIL_MSGVP_ENHANCED:
            fRet = ParseEnhancedValidityPeriod(pbIn, rstVP, rcbParsed);
            goto Error;

        case RIL_MSGVP_ABSOLUTE:
            // An absolute VP is the same as a normal TimeStamp, so call the appropriate function
            fRet = ParseMsgTimeStamp(pbIn, rstVP, rcbParsed);
            goto Error;

        default:
            goto Error;
    }
    fRet = TRUE;

Error:
    return fRet;
}


//
// Helper function to parse a Deliver SMS message
//
static BOOL ParseDeliverMsg(const BYTE* const pbIn, const UINT cbIn, RILMESSAGE& rrmMsg)
{
    TBD_FUNCTION(ParseDeliverMsg);
    TBD_ASSERT(NULL != pbIn);
    TBD_ASSERT(0 < cbIn);

    UINT cbParsed;
    const BYTE* pbWalk;
    const BYTE* pbEnd;
    BOOL fRet = FALSE;

    pbWalk = pbIn;
    pbEnd = pbIn + cbIn;

    // Set Type.  See GSM 03.40 section 9.2.3.1
    rrmMsg.dwType = RIL_MSGTYPE_IN_DELIVER;
    rrmMsg.dwParams |= RIL_PARAM_M_TYPE;

    // Set dwFlags
    rrmMsg.dwFlags = RIL_MSGFLAG_NONE;
    rrmMsg.dwParams |= RIL_PARAM_M_FLAGS;

    // GSM 03.40 section 9.2.3.2
    if (!(*pbWalk & 0x04)) {
        rrmMsg.dwFlags |= RIL_MSGFLAG_MORETOSEND;
    }
    // GSM 03.40 section 9.2.3.4
    if (*pbWalk & 0x20) {
        rrmMsg.dwFlags |= RIL_MSGFLAG_STATUSREPORTRETURNED;
    }
    // GSM 03.40 section 9.2.3.23
    if (*pbWalk & 0x40) {
        rrmMsg.dwFlags |= RIL_MSGFLAG_HEADER;
    }
    // GSM 03.40 section 9.2.3.17
    if (*pbWalk & 0x80) {
        rrmMsg.dwFlags |= RIL_MSGFLAG_REPLYPATH;
    }
    pbWalk++;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse address
    if (!ParseMsgAddress(pbWalk, FALSE, rrmMsg.msgInDeliver.raOrigAddress, cbParsed, TRUE)) {
        goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_ORIGADDRESS;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse protocol ID
    if (!ParseMsgProtocolID(pbWalk, rrmMsg.msgInDeliver.dwProtocolID, cbParsed)) {
        goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_PROTOCOLID;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse DCS
    if (!ParseMsgDCS(pbWalk, rrmMsg.msgInDeliver.rmdDataCoding, cbParsed)) {
        goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_DATACODING;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse time stamp
    if (!ParseMsgTimeStamp(pbWalk, rrmMsg.msgInDeliver.stSCReceiveTime, cbParsed)) {
        goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_SCRECEIVETIME;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse message header and body
    if(!ParseMsgHeaderAndBody(pbWalk, pbEnd, rrmMsg.dwFlags, rrmMsg.msgInDeliver.rmdDataCoding, rrmMsg.dwParams,
                          rrmMsg.msgInDeliver.rgbHdr, rrmMsg.msgInDeliver.rgbMsg, rrmMsg.msgInDeliver.cbHdrLength,
                          rrmMsg.msgInDeliver.cchMsgLength, cbParsed)) {
        goto Error;
    }
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }
    fRet = TRUE;

Error:
    return fRet;
}


//
// Helper function to parse a Status Report SMS message
//
static BOOL ParseStatusMsg(const BYTE* const pbIn, const UINT cbIn, RILMESSAGE& rrmMsg)
{
    TBD_FUNCTION(ParseStatusMsg);
    TBD_ASSERT(NULL != pbIn);
    TBD_ASSERT(0 < cbIn);

    UINT cbParsed;
    BYTE bParamInd;
    const BYTE* pbWalk;
    const BYTE* pbEnd;
    BOOL fRet = FALSE;

    pbWalk = pbIn;
    pbEnd = pbIn + cbIn;

    // Set message Type.  see GSM 03.40 section 9.2.3.1
    rrmMsg.dwType = RIL_MSGTYPE_IN_STATUS;
    rrmMsg.dwParams |= RIL_PARAM_M_TYPE;

    // Set flags
    rrmMsg.dwParams |= RIL_PARAM_M_FLAGS;
    rrmMsg.dwFlags = RIL_MSGFLAG_NONE;

    if (!(*pbWalk & 0x04)) {
        // GSM 03.40 section 9.2.3.2
        rrmMsg.dwFlags |= RIL_MSGFLAG_MORETOSEND;
    }
    if (*pbWalk & 0x20) {
        // GSM 03.40 section 9.2.3.26, TP-Status-Report-Qualifier
        rrmMsg.dwFlags |= RIL_MSGFLAG_CAUSEDBYCOMMAND;
    }
    if (*pbWalk & 0x40) {
        // GSM 03.40 section 9.2.3.23
        rrmMsg.dwFlags |= RIL_MSGFLAG_HEADER;
    }
    pbWalk++;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse message reference
    rrmMsg.msgInStatus.dwTgtMsgReference = *pbWalk++;
    rrmMsg.dwParams |= RIL_PARAM_M_TGTMSGREFERENCE;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse address
    if (!ParseMsgAddress(pbWalk, FALSE, rrmMsg.msgInStatus.raTgtRecipAddress, cbParsed, TRUE)) {
        goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_TGTRECIPADDRESS;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse receive time
    if (!ParseMsgTimeStamp(pbWalk, rrmMsg.msgInStatus.stTgtSCReceiveTime, cbParsed)) {
        goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_TGTSCRECEIVETIME;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse discharge time
    if (!ParseMsgTimeStamp(pbWalk, rrmMsg.msgInStatus.stTgtDischargeTime, cbParsed)) {
        goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_TGTDISCHARGETIME;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse message status value
    if (!ParseMsgStatus(pbWalk, rrmMsg.msgInStatus.dwTgtDlvStatus, cbParsed)) {
        goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_TGTDLVSTATUS;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Check Parameter indicator, GSM 03.40 section 9.2.3.27
    if (pbWalk < pbEnd) {
        bParamInd = *pbWalk++;
        if (pbWalk > pbEnd) {
            goto Error;
        }

        // Check for ProtocolID
        if (bParamInd & 0x01) {
            if (!ParseMsgProtocolID(pbWalk, rrmMsg.msgInStatus.dwProtocolID, cbParsed)) {
                goto Error;
            }
            rrmMsg.dwParams |= RIL_PARAM_M_PROTOCOLID;
            pbWalk += cbParsed;
            if (pbWalk > pbEnd) {
                goto Error;
            }
        }

        // Check for DCS
        if (bParamInd & 0x02) {
            if (!ParseMsgDCS(pbWalk, rrmMsg.msgInStatus.rmdDataCoding, cbParsed)) {
                goto Error;
            }
            rrmMsg.dwParams |= RIL_PARAM_M_DATACODING;
            pbWalk += cbParsed;
            if (pbWalk > pbEnd) {
                goto Error;
            }
        }

        // Check for UDL
        if (bParamInd & 0x04) {
            // Parse message header and body
            if(!ParseMsgHeaderAndBody(pbWalk, pbEnd, rrmMsg.dwFlags, rrmMsg.msgInStatus.rmdDataCoding, rrmMsg.dwParams,
                                  rrmMsg.msgInStatus.rgbHdr, rrmMsg.msgInStatus.rgbMsg, rrmMsg.msgInStatus.cbHdrLength,
                                  rrmMsg.msgInStatus.cchMsgLength, cbParsed)) {
                goto Error;
            }
            pbWalk += cbParsed;
            if (pbWalk > pbEnd) {
                goto Error;
            }
        }
    }
    fRet = TRUE;

Error:
    return fRet;
}


//
// Parse a Submit message
//
static BOOL ParseSubmitMsg(const BYTE* const pbIn, const UINT cbIn, RILMESSAGE& rrmMsg)
{
    TBD_FUNCTION(ParseSubmitMsg);
    TBD_ASSERT(NULL != pbIn);
    TBD_ASSERT(0 < cbIn);

    UINT cbParsed;
    const BYTE* pbWalk;
    const BYTE* pbEnd;
    BOOL fRet = FALSE;

    pbWalk = pbIn;
    pbEnd = pbIn + cbIn;

    // Set Type.  see GSM 03.40 section 9.2.3.1
    rrmMsg.dwParams |= RIL_PARAM_M_TYPE;
    rrmMsg.dwType = RIL_MSGTYPE_OUT_SUBMIT;

    // Set dwFlags
    rrmMsg.dwParams |= RIL_PARAM_M_FLAGS;
    rrmMsg.dwFlags = RIL_MSGFLAG_NONE;

    if (*pbWalk & 0x04) {
        // GSM 03.40 section 9.2.3.25
        rrmMsg.dwFlags |= RIL_MSGFLAG_REJECTDUPS;
    }
    if (*pbWalk & 0x20) {
        // GSM 03.40 section 9.2.3.5
        rrmMsg.dwFlags |= RIL_MSGFLAG_STATUSREPORTREQUESTED;
    }
    if (*pbWalk & 0x40) {
        // GSM 03.40 section 9.2.3.23
        rrmMsg.dwFlags |= RIL_MSGFLAG_HEADER;
    }
    if (*pbWalk & 0x80) {
        // GSM 03.40 sectoin 9.2.3.17
        rrmMsg.dwFlags |= RIL_MSGFLAG_REPLYPATH;
    }

    // Parse Validity Period Format.  see GSM 03.40 section 9.2.3.3
    switch((*pbWalk & 0x18) >> 3)
    {
        case 0x00:
            rrmMsg.msgOutSubmit.dwVPFormat = RIL_MSGVP_NONE;
            break;

        case 0x01:
            rrmMsg.msgOutSubmit.dwVPFormat = RIL_MSGVP_ENHANCED;
            break;

        case 0x02:
            rrmMsg.msgOutSubmit.dwVPFormat = RIL_MSGVP_RELATIVE;
            break;

        case 0x03:
            rrmMsg.msgOutSubmit.dwVPFormat = RIL_MSGVP_ABSOLUTE;
            break;

        default:
            goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_VPFORMAT;
    pbWalk++;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Skip the message reference
    pbWalk++;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse address
    if (!ParseMsgAddress(pbWalk, FALSE, rrmMsg.msgOutSubmit.raDestAddress, cbParsed, TRUE)) {
        goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_DESTADDRESS;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse protocol ID
    if (!ParseMsgProtocolID(pbWalk, rrmMsg.msgOutSubmit.dwProtocolID, cbParsed)) {
        goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_PROTOCOLID;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse DCS
    if (!ParseMsgDCS(pbWalk, rrmMsg.msgOutSubmit.rmdDataCoding, cbParsed)) {
        goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_DATACODING;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse validity period
    if (!ParseMsgValidityPeriod(pbWalk, rrmMsg.msgOutSubmit.dwVPFormat, rrmMsg.msgOutSubmit.stVP, cbParsed)) {
        goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_VP;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse message header and body
    if(!ParseMsgHeaderAndBody(pbWalk, pbEnd, rrmMsg.dwFlags, rrmMsg.msgOutSubmit.rmdDataCoding, rrmMsg.dwParams,
                          rrmMsg.msgOutSubmit.rgbHdr, rrmMsg.msgOutSubmit.rgbMsg, rrmMsg.msgOutSubmit.cbHdrLength,
                          rrmMsg.msgOutSubmit.cchMsgLength, cbParsed)) {
        goto Error;
    }
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }
    fRet = TRUE;

Error:
    return fRet;
}


//
// Parse an SMS-COMMAND message
//
static BOOL ParseCommandMsg(const BYTE* const pbIn, const UINT cbIn, RILMESSAGE& rrmMsg)
{
    TBD_FUNCTION(ParseCommandMsg);
    TBD_ASSERT(NULL != pbIn);
    TBD_ASSERT(0 < cbIn);

    UINT cbParsed;
    const BYTE* pbWalk;
    const BYTE* pbEnd;
    BOOL fRet = FALSE;

    pbWalk = pbIn;
    pbEnd = pbIn + cbIn;

    // Set Type.  see GSM 03.40 section 9.2.3.1
    rrmMsg.dwParams |= RIL_PARAM_M_TYPE;
    rrmMsg.dwType = RIL_MSGTYPE_OUT_COMMAND;

    // Set dwFlags
    rrmMsg.dwParams |= RIL_PARAM_M_FLAGS;
    rrmMsg.dwFlags = RIL_MSGFLAG_NONE;

    if (*pbWalk & 0x20) {
        // GSM 03.40 section 9.2.3.5
        rrmMsg.dwFlags |= RIL_MSGFLAG_STATUSREPORTREQUESTED;
    }
    if (*pbWalk & 0x40) {
        // GSM 03.40 section 9.2.3.23
        rrmMsg.dwFlags |= RIL_MSGFLAG_HEADER;
    }

    // Skip the message reference
    pbWalk++;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse protocol ID
    if (!ParseMsgProtocolID(pbWalk, rrmMsg.msgOutCommand.dwProtocolID, cbParsed)) {
        goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_PROTOCOLID;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse command type
    if (!ParseMsgCommandType(pbWalk, rrmMsg.msgOutCommand.dwCommandType, cbParsed)) {
        goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_COMMANDTYPE;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse the target message reference
    rrmMsg.msgOutCommand.dwTgtMsgReference = *pbWalk++;
    rrmMsg.dwParams |= RIL_PARAM_M_TGTMSGREFERENCE;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse address
    if (!ParseMsgAddress(pbWalk, FALSE, rrmMsg.msgOutCommand.raDestAddress, cbParsed, TRUE)) {
        goto Error;
    }
    rrmMsg.dwParams |= RIL_PARAM_M_DESTADDRESS;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse command data length
    rrmMsg.msgOutCommand.cbCmdLength = *pbWalk++;
    rrmMsg.dwParams |= RIL_PARAM_M_CMDLENGTH;
    if (pbWalk > pbEnd) {
        goto Error;
    }

    // Parse command data
    (void)memcpy(rrmMsg.msgOutCommand.rgbCmd, pbWalk, min(rrmMsg.msgOutCommand.cbCmdLength, MAXLENGTH_CMD));
    rrmMsg.dwParams |= RIL_PARAM_M_CMD;
    pbWalk += cbParsed;
    if (pbWalk > pbEnd) {
        goto Error;
    }
    fRet = TRUE;

Error:
    return fRet;
}


//
// Parse an Incoming SMS message into a RILMESSAGE structure
//
HRESULT ParseSMSMessage(const LPCSTR sIn, const UINT cbIn, BOOL fIncoming, BOOL fPrependedSMSC, RILMESSAGE& rrmMsg)
{
    TBD_FUNCTION(ParseSMSMessage);
    TBD_ASSERT(NULL != sIn);
    TBD_ASSERT(0 < cbIn);

    BYTE* pbGSMBytes = NULL;
    UINT cbGSMBytes;
    UINT cbParsed = 0;
    BYTE bMsgType;
    BOOL fRet = TRUE;
    HRESULT hr = S_OK;

    // Zero out the message structure
    (void)memset(&rrmMsg, 0x00, sizeof(RILMESSAGE));
    rrmMsg.cbSize = sizeof(RILMESSAGE);

    // HW-SPECIFIC: WaveCom Series 2 hardware doesn't encode the SMSC address prepended to a message

    if ('+' == *sIn) {
        UINT i;
        LPWSTR pwchAddress;
        LPCSTR pchIn;
        LPCSTR sAfterSMSCAddress;
        UINT cbAfterSMSCAddress;

        // Assume the type and numplan
        rrmMsg.raSvcCtrAddress.dwType = RIL_ADDRTYPE_INTERNATIONAL;
        rrmMsg.raSvcCtrAddress.dwNumPlan = RIL_NUMPLAN_TELEPHONE;

        // Copy the phone number over
        pwchAddress = rrmMsg.raSvcCtrAddress.wszAddress;
        pchIn = sIn + 1;
        for (i = 0; i < SMS_BOGUS_ADDR_LENGTH ; i++) {
            *pwchAddress++ = *pchIn & 0x00ff;
        }
        *pwchAddress = L'\0';

        // Set approproiate param flags
        rrmMsg.raSvcCtrAddress.dwParams |= (RIL_PARAM_A_TYPE | RIL_PARAM_A_NUMPLAN | RIL_PARAM_A_ADDRESS);
        rrmMsg.dwParams |= RIL_PARAM_M_SVCCTRADDRESS;

        // Convert the rest of the message from GSM default HEX represention into GSM default
        sAfterSMSCAddress = sIn + SMS_BOGUS_ADDR_LENGTH;
        cbAfterSMSCAddress = cbIn - SMS_BOGUS_ADDR_LENGTH;
        pbGSMBytes = new BYTE[cbAfterSMSCAddress/ 2 + 1];
        if (!pbGSMBytes) {
            hr = E_OUTOFMEMORY;
            goto Error;
        }
        if (!GSMHexToGSM(sAfterSMSCAddress, cbAfterSMSCAddress, (LPSTR)pbGSMBytes, cbAfterSMSCAddress / 2 + 1, cbGSMBytes)) {
            hr = E_FAIL;
            goto Error;
        }
    } else {

    // Convert the whole message from GSM default HEX represention into GSM default
    pbGSMBytes = new BYTE[cbIn / 2 + 1];
    if (!pbGSMBytes) {
        hr = E_OUTOFMEMORY;
        goto Error;
    }
    if (!GSMHexToGSM(sIn, cbIn, (LPSTR)pbGSMBytes, cbIn / 2 + 1, cbGSMBytes)) {
        hr = E_FAIL;
        goto Error;
    }

    if (fPrependedSMSC)
    {
        // Grab the prepended SMSC address
        if (!ParseMsgAddress(pbGSMBytes, TRUE, rrmMsg.raSvcCtrAddress, cbParsed, FALSE)) {
            hr = E_FAIL;
            goto Error;
        }
        rrmMsg.dwParams |= RIL_PARAM_M_SVCCTRADDRESS;
    }

    }

    // Mask the bottom two bits to get the message type (GSM 03.40 section 9.2.3.1)
    bMsgType = *(pbGSMBytes + cbParsed) & 0x03;
    switch (bMsgType)
    {
        case 0x00:  // SMS-DELIVER or SMS-DELIVER-REPORT
            if (!fIncoming) {
                // We don't support SMS-DELIVER-REPORTs
                hr = E_FAIL;
                goto Error;
            }
            fRet = ParseDeliverMsg(pbGSMBytes + cbParsed, cbGSMBytes - cbParsed, rrmMsg);
            break;

        case 0x01:  // SMS-SUBMIT or SMS-SUBMIT-REPORT
            if (fIncoming) {
                // We don't support SMS-SUBMIT-REPORTs
                hr = E_FAIL;
                goto Error;
            }
            fRet = ParseSubmitMsg(pbGSMBytes + cbParsed, cbGSMBytes - cbParsed, rrmMsg);
            break;

        case 0x02:  // SMS-STATUS-REPORT or SMS_COMMAND
            if (fIncoming) {
                fRet = ParseStatusMsg(pbGSMBytes + cbParsed, cbGSMBytes - cbParsed, rrmMsg);
            } else {
                fRet = ParseCommandMsg(pbGSMBytes + cbParsed, cbGSMBytes - cbParsed, rrmMsg);
            }
            break;

        default:
            hr = E_FAIL;
            TBD_OUTPUT(TBDCT_INFO, TBDOL_ERROR, TEXT("ParseSMSMessage : Invalid Message Type"));
            goto Error;
    }

    if (!fRet) {
        TBD_OUTPUT(TBDCT_INFO, TBDOL_ERROR, TEXT("ParseSMSMessage : Failed"));
        hr = E_FAIL;
    }

Error:
    delete[] pbGSMBytes;
    return hr;
}


//
// Helper for setting RILMSGDCS language properties
//
static void SetDCSLanguageType(RILMSGDCS& rrmdDCS, DWORD dwAlphabet, DWORD dwLanguage)
{
    rrmdDCS.dwType = RIL_DCSTYPE_LANGUAGE;
    rrmdDCS.dwParams |= RIL_PARAM_MDCS_TYPE;

    rrmdDCS.dwAlphabet = dwAlphabet;
    rrmdDCS.dwParams |= RIL_PARAM_MDCS_ALPHABET;

    rrmdDCS.dwLanguage = dwLanguage;
    rrmdDCS.dwParams |= RIL_PARAM_MDCS_LANGUAGE;
}

//
// Set Data Coding Scheme of Incoming Cell Broadcast Message
// see GSM 03.38
//
static BOOL ParseCellBroadcastDCS(const BYTE bIn, RILMSGDCS& rrmdDCS, UINT& rcbLangInMsgBody)
{
    TBD_FUNCTION(ParseCellBroadcastDCS);

    BOOL fRet = FALSE;

    rcbLangInMsgBody = 0;

    (void)memset(&rrmdDCS, 0x00, sizeof(RILMSGDCS));
    rrmdDCS.cbSize = sizeof(RILMSGDCS);

    switch (bIn & 0xf0)
    {
        case 0x00:
            SetDCSLanguageType(rrmdDCS, RIL_DCSALPHABET_DEFAULT, g_rgdwDCSLanguages[bIn & 0x0f]);
            break;

        case 0x10:
            switch (bIn & 0x0f)
            {
                case 0x00:
                    // First 3 characters of message body contain language.
                    rcbLangInMsgBody = 3;

                    SetDCSLanguageType(rrmdDCS, RIL_DCSALPHABET_DEFAULT, RIL_DCSLANG_UNKNOWN);
                    break;

                case 0x01:
                    // First 2 characters of message body contain language.
                    rcbLangInMsgBody = 2;

                    SetDCSLanguageType(rrmdDCS, RIL_DCSALPHABET_UCS2, RIL_DCSLANG_UNKNOWN);
                    break;

                default:
                    SetDCSLanguageType(rrmdDCS, RIL_DCSALPHABET_DEFAULT, RIL_DCSLANG_UNKNOWN);
                    break;
            }
            break;

        case 0x20:
            if (0x00 == (bIn & 0x0f))
            {
                SetDCSLanguageType(rrmdDCS, RIL_DCSALPHABET_DEFAULT, RIL_DCSLANG_CZECH);
                break;
            }
            // else fall through...

        case 0x30:
            SetDCSLanguageType(rrmdDCS, RIL_DCSALPHABET_DEFAULT, RIL_DCSLANG_UNKNOWN);
            break;

        case 0xf0:
            rrmdDCS.dwType = RIL_DCSTYPE_GENERAL;
            rrmdDCS.dwParams |= RIL_PARAM_MDCS_TYPE;

            if (bIn & 0x04)
            {
                rrmdDCS.dwAlphabet = RIL_DCSALPHABET_8BIT;
                rrmdDCS.dwParams |= RIL_PARAM_MDCS_ALPHABET;
            }
            else
            {
                rrmdDCS.dwAlphabet = RIL_DCSALPHABET_DEFAULT;
                rrmdDCS.dwParams |= RIL_PARAM_MDCS_ALPHABET;
            }

            switch (bIn & 0x03)
            {
                case 0x00:
                    // None
                    break;

                case 0x01:
                    rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_1;
                    rrmdDCS.dwParams |= RIL_PARAM_MDCS_MSGCLASS;
                    break;

                case 0x02:
                    rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_2;
                    rrmdDCS.dwParams |= RIL_PARAM_MDCS_MSGCLASS;
                    break;

                case 0x03:
                    rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_3;
                    rrmdDCS.dwParams |= RIL_PARAM_MDCS_MSGCLASS;
                    break;
            }
            break;

        default:
            if (bIn & 0x40)
            {
                rrmdDCS.dwType = RIL_DCSTYPE_GENERAL;
                rrmdDCS.dwParams |= RIL_PARAM_MDCS_TYPE;

                if (bIn & 0x20)
                {
                    rrmdDCS.dwFlags |= RIL_DCSFLAG_COMPRESSED;
                    rrmdDCS.dwParams |= RIL_PARAM_MDCS_FLAGS;
                }

                if (bIn & 0x10)
                {
                    switch (bIn & 0x03)
                    {
                        case 0x00:
                            rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_0;
                            rrmdDCS.dwParams |= RIL_PARAM_MDCS_MSGCLASS;
                            break;

                        case 0x01:
                            rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_1;
                            rrmdDCS.dwParams |= RIL_PARAM_MDCS_MSGCLASS;
                            break;

                        case 0x02:
                            rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_2;
                            rrmdDCS.dwParams |= RIL_PARAM_MDCS_MSGCLASS;
                            break;

                        case 0x03:
                            rrmdDCS.dwMsgClass = RIL_DCSMSGCLASS_3;
                            rrmdDCS.dwParams |= RIL_PARAM_MDCS_MSGCLASS;
                            break;
                    }
                }

                switch ((bIn >> 2) & 0x03)
                {
                    case 0x00:
                        rrmdDCS.dwAlphabet = RIL_DCSALPHABET_DEFAULT;
                        rrmdDCS.dwParams |= RIL_PARAM_MDCS_ALPHABET;
                        break;

                    case 0x01:
                        rrmdDCS.dwAlphabet = RIL_DCSALPHABET_8BIT;
                        rrmdDCS.dwParams |= RIL_PARAM_MDCS_ALPHABET;
                        break;

                    case 0x02:
                        rrmdDCS.dwAlphabet = RIL_DCSALPHABET_UCS2;
                        rrmdDCS.dwParams |= RIL_PARAM_MDCS_ALPHABET;
                        break;

                    case 0x03:
                        // Reserved... so use assume alphabet
                        rrmdDCS.dwAlphabet = RIL_DCSALPHABET_DEFAULT;
                        rrmdDCS.dwParams |= RIL_PARAM_MDCS_ALPHABET;
                        break;
                }
            }
            else
            {
                // All other cases are reserved and assumed to use default alphabet.
                SetDCSLanguageType(rrmdDCS, RIL_DCSALPHABET_DEFAULT, RIL_DCSLANG_UNKNOWN);
            }
            break;
    }


    return TRUE;
}


//
// Parses header information for
//
static BOOL ParseCellBroadcastHeader(const BYTE* const pbIn, const UINT cbIn, RILMESSAGE& rrmMsg, UINT& rcbParsed)
{
    rcbParsed = 0;

    if (cbIn < CELLBROADCAST_HEADER_LENGTH)
    {
        return FALSE;
    }

    // Parse values from the first 2 octets (the serial number)
    WORD wSerialNumber                  = MAKEWORD(*(pbIn+1), *pbIn);
    rrmMsg.msgBcGeneral.dwGeoScope      = GEOSCOPE_FROM_SERIALNUMBER    (wSerialNumber);
    rrmMsg.msgBcGeneral.dwMsgCode       = MESSAGECODE_FROM_SERIALNUMBER (wSerialNumber);
    rrmMsg.msgBcGeneral.dwUpdateNumber  = UPDATENUMBER_FROM_SERIALNUMBER(wSerialNumber);

    // Parse the message ID
    rrmMsg.msgBcGeneral.dwID            = MAKEWORD(*(pbIn+3), *(pbIn+2));

    // Parse DCS info
    UINT cbLanguageInMessageBody = 0;
    if (!ParseCellBroadcastDCS(*(pbIn+4), rrmMsg.msgBcGeneral.rmdDataCoding, cbLanguageInMessageBody))
    {
        return FALSE;
    }

    // Parse page info
    BYTE bPageInfo = *(pbIn+5);
    rrmMsg.msgBcGeneral.dwTotalPages    = TOTALPAGES(bPageInfo);
    rrmMsg.msgBcGeneral.dwPageNumber    = PAGENUMBER(bPageInfo);

    if (rrmMsg.msgBcGeneral.dwTotalPages == 0 ||
        rrmMsg.msgBcGeneral.dwPageNumber == 0)
    {
        rrmMsg.msgBcGeneral.dwTotalPages = 1;
        rrmMsg.msgBcGeneral.dwPageNumber = 1;
    }

    rcbParsed = CELLBROADCAST_HEADER_LENGTH + cbLanguageInMessageBody;

    return TRUE;
}

HRESULT ParseCellBroadcastMessage(const LPCSTR sIn, const UINT cbIn, RILMESSAGE& rrmMsg)
{
    TBD_FUNCTION(ParseCellBroadcastMessage);
    TBD_ASSERT(NULL != sIn);
    TBD_ASSERT(0 < cbIn);

    HRESULT hr = S_OK;
    BYTE* pbGSMBytes = NULL;
    UINT cbGSMBytes;
    UINT cbParsed = 0;

    // Zero out the message structure
    (void)memset(&rrmMsg, 0x00, sizeof(RILMESSAGE));
    rrmMsg.cbSize = sizeof(RILMESSAGE);

    // Convert the whole message from GSM default HEX represention into GSM default
    pbGSMBytes = new BYTE[cbIn / 2 + 1];
    if (!pbGSMBytes) {
        hr = E_OUTOFMEMORY;
        goto Error;
    }
    if (!GSMHexToGSM(sIn, cbIn, (LPSTR)pbGSMBytes, cbIn / 2 + 1, cbGSMBytes)) {
        hr = E_FAIL;
        goto Error;
    }

    // Parse the header information
    if (!ParseCellBroadcastHeader(pbGSMBytes, cbGSMBytes, rrmMsg, cbParsed))
    {
        hr = E_FAIL;
        goto Error;
    }

    // This is a cell broadcast message.
    rrmMsg.dwType = RIL_MSGTYPE_BC_GENERAL;

    // Copy the message body to the output structure
    rrmMsg.msgBcGeneral.cchMsgLength = min(cbGSMBytes - cbParsed, MAXLENGTH_MSG);
    memcpy(rrmMsg.msgBcGeneral.rgbMsg, pbGSMBytes + cbParsed, rrmMsg.msgBcGeneral.cchMsgLength);

    // We always get all the parameters for cell broadcast
    rrmMsg.dwParams = RIL_PARAM_M_ALL_BC_GENERAL;

Error:
    delete[] pbGSMBytes;
    return hr;
}


